namespace YM {
    export class EntityStatus {
        public static None: number = 0;
        public static IsFromPool: number = 1;
        public static IsRegister: number = 2;
        public static IsComponent: number = 4;
    }
    export class Entity extends YMObject implements IEqualityComparable {

        private static readonly hashSetPool: Pool<HashSet<Entity>> = new Pool<HashSet<Entity>>();
        private static readonly dictPool: Pool<Map<{ new() }, Entity>> = new Pool<Map<{ new() }, Entity>>();
        private static readonly childrenPool: Pool<Map<number, Entity>> = new Pool<Map<number, Entity>>();
        private _instanceId: number;
        public get instanceId(): number {
            return this._instanceId;
        }
        public set instanceId(value: number) {
            this._instanceId = value;
        }
        public static get global(): cc.Node {
            return cc.Canvas.instance.node;
        }

        private status: number = EntityStatus.None;

        public get isFromPool(): boolean {
            return (this.status & EntityStatus.IsFromPool) == EntityStatus.IsFromPool;
        }
        public set isFromPool(value: boolean) {
            if (value) {
                this.status |= EntityStatus.IsFromPool;
            }
            else {
                this.status &= ~EntityStatus.IsFromPool;
            }

            if (this.instanceId == 0) {
                this.instanceId = IdGenerater.GenerateInstanceId();
            }

            this.isRegister = value;
        }
        private get isRegister(): boolean {
            return (this.status & EntityStatus.IsRegister) == EntityStatus.IsRegister;
        }
        private set isRegister(value: boolean) {
            if (this.isRegister == value) {
                return;
            }
            if (value) {
                this.status |= EntityStatus.IsRegister;
            }
            else {
                this.status &= ~EntityStatus.IsRegister;
            }
            game.event.add(this,value);
        }
        private get isComponent(): boolean {
            return (this.status & EntityStatus.IsComponent) == EntityStatus.IsComponent;
        }
        private set isComponent(value: boolean) {
            if (value) {
                this.status |= EntityStatus.IsComponent;
            }
            else {
                this.status &= ~EntityStatus.IsComponent;
            }
        }
        public get isDisposed(): boolean {
            return this.instanceId == 0;
        }
        protected _parent: Entity;
        public get parent(): Entity {
            return this._parent;

        }
        public set parent(value: Entity) {
            if (value == null) {
                logError(`cant set parent null: ${this.constructor.name}`);
                return;
            }

            if (this._parent != null) // 之前有parent
            {
                // parent相同，不设置
                if (this._parent.instanceId == value.instanceId) {
                    logWarn(`重复设置了Parent: ${this.getType().name} parent: ${this.getType().name}`);
                    return;
                }

                this._parent.removeChild(this);
                this._parent = value;
                this._parent.addChild(this);

                this.domain = this._parent.domain;
            }
            else {
                this._parent = value;
                this._parent.addChild(this);

                this.isComponent = false;

                this.afterSetParent();
            }
        }
        private set componentParent(value: Entity) {
            if (this.parent != null) {
                logError(`Component parent is null: ${this.constructor.name}`);
            }

            this.parent = value;

            this.isComponent = true;

            this.afterSetParent();
        }
        private afterSetParent(): void {
            if (this.parent.domain != null) {
                this.domain = this.parent.domain;
            }

            // 检测自己的domain是不是跟父亲一样
            if (this.domain != null && this.parent.domain != null && this.domain.instanceId != this.parent.domain.instanceId && !(this instanceof Scene)) {
                logError(`自己的domain跟parent不一样: ${this.constructor.name}`);
            }
        }
        public getParent<T extends Entity>(): T {
            return this.parent as T;
        }
        private _id: number;
        public get id(): number {
            return this._id;
        }
        public set id(value: number) {
            this._id = value;
        }
        protected _domain: Entity;
        public get domain(): Entity {
            return this._domain;
        }
        public set domain(value: Entity) {
            if (value == null) {
                return;
            }
            let preDomain = this._domain;
            this._domain = value;

            // if (!(domain is Scene))
            // {
            //     throw new Exception($"domain is not scene: {GetType().Name}");
            // }

            this._domain = value;

            // 是否注册跟parent一致
            if (this.parent != null) {
                this.isRegister = this.parent.isRegister;
            }

            // 递归设置孩子的Domain
            if (this.children != null) {
                for (let entity of this.children.values()) {
                    entity.domain = this._domain;
                }
            }

            if (this.components != null) {
                for (let component of this.components.values()) {
                    component.domain = this._domain;
                }
            }

            if (preDomain == null && !this.isFromPool) {
                //Game.EventSystem.Deserialize(this);
            }
        }
        private _children: Map<number, Entity>;

        public get children(): Map<number, Entity> {
            if (this._children == null) {
                this._children = Entity.childrenPool.fetch(Map);
            }

            return this._children;
        }
        
        private _enabled : boolean=true;
        public get enabled() : boolean {
            return this._enabled;
        }
        public set enabled(v : boolean) {
            if(this._enabled==v){
                return;
            }
            this._enabled = v;
            this.setEnable(v);
            if (this._children != null) {
                for (let child of this._children) {
                    child[1].enabled=v;
                }
            }
            if (this._components != null){
                for (let comp of this._components) {
                    comp[1].enabled=v;
                }
            }
        }
        private setEnable(enabled:boolean){
            if(enabled){
                game.event.addSystem(this);
                this.onEnable();
            }
            else{
                game.event.removeSystem(this);
                this.onDisable();
            }
        }
        private addChild(entity: Entity): void {
            this.children.set(entity.id, entity);
        }

        private removeChild(entity: Entity): void {
            if (this._children == null) {
                return;
            }
            
            this._children.delete(entity.id);

            if (this._children.size == 0) {
                Entity.childrenPool.recycle(this._children);
                this._children = null;
            }
        }
        private _components: Map<{ new() }, Entity>;
        public get components(): Map<{ new() }, Entity> {
            return this._components;
        }
        constructor() {
            super();
            this.instanceId = IdGenerater.GenerateInstanceId();
        }
        public  dispose(): void {
            if (this.isDisposed) {
                return;
            }

            let instanceId: number = this.instanceId;
            this.instanceId = 0;

            game.event.remove(instanceId);
            // 触发Destroy事件
            game.event.destroy(this);

            this.domain = null;
            // 清理Children
            if (this._children != null) {
                var deletes = this.children;
                this._children = null;

                for (let child of deletes.values()) {
                    child.dispose();
                }

                deletes.clear();
                Entity.childrenPool.recycle(deletes);

            }

            // 清理Component
            if (this._components != null) {
                let deletes = this._components;
                this._components = null;
                for (var kv of deletes) {
                    kv[1].dispose();
                }

                deletes.clear();
                Entity.dictPool.recycle(deletes);

                if (this.isComponent) {
                    //this._parent.removeComponentWithInstance(this);
                }
                else {
                    this._parent.removeChild(this);
                }

                this._parent = null;

                if (this.isFromPool) {
                    game.objectPool.recycle(this);
                }
                // else {
                //     if (this.node != null) {
                //         this.node.destroy();
                //     }
                // }

                this.status = EntityStatus.None;
            }
        }
        private addToComponent(type:{new()}, component:Entity):void
		{
			if (this._components == null)
			{
				this._components = Entity.dictPool.fetch(Map);
			}

			this._components.set(type, component);
			
        }
        private removeFromComponent(type:{new()}, component:Entity):void
		{
			if (this._components == null)
			{
				return;
			}
			
			this._components.delete(type);
			
			if (this._components.size == 0 && this.isFromPool)
			{
				Entity.dictPool.recycle(this._components);
				this._components = null;
			}
			
        }
        public addComponentWithInstance(component:Entity):Entity
		{
			component.componentParent = this;
			
			let type = component.getType();
			
			this.addToComponent(type, component);

			return component;
        }
        public addComponent<K extends Entity>(type:{new():K}):K;
        public addComponent<K extends Entity,P1>(type:{new():K},p1:P1):K;
        public addComponent<K extends Entity,P1,P2>(type:{new():K},p1:P1,p2:P2):K;
        public addComponent<K extends Entity,P1,P2,P3>(type:{new():K},p1:P1,p2:P2,p3:P3):K;
        public addComponent<K extends Entity,P1,P2,P3>(type:{new():K},p1?:P1,p2?:P2,p3?:P3):K
		{
			
            let component:K;
            if(!p1&&!p2&&!p3){
                component = this.createWithComponentParent1(type);
            }
            else if(p1&&!p2&&!p3){
                component = this.createWithComponentParent2(type,p1);
            }
            else if(p1&&p2&&!p3){
                component = this.createWithComponentParent3(type,p1,p2);
            }
            else if(p1&&p2&&p3){
                component = this.createWithComponentParent4(type,p1,p2,p3);
            }
			this.addToComponent(type, component);
			
			return component;
        }
        public addComponentNoPool<K extends Entity>(type:{new():K}):K;
        public addComponentNoPool<K extends Entity,P1>(type:{new():K},p1:P1):K;
        public addComponentNoPool<K extends Entity,P1,P2>(type:{new():K},p1:P1,p2:P2):K;
        public addComponentNoPool<K extends Entity,P1,P2,P3>(type:{new():K},p1:P1,p2:P2,p3:P3):K;
        public addComponentNoPool<K extends Entity,P1,P2,P3>(type:{new():K},p1?:P1,p2?:P2,p3?:P3):K
		{
			
            let component:K;
            if(!p1&&!p2&&!p3){
                component = this.createWithComponentParent1(type,false);
            }
            else if(p1&&!p2&&!p3){
                component = this.createWithComponentParent2(type,p1,false);
            }
            else if(p1&&p2&&!p3){
                component = this.createWithComponentParent3(type,p1,p2,false);
            }
            else if(p1&&p2&&p3){
                component = this.createWithComponentParent4(type,p1,p2,p3,false);
            }
			this.addToComponent(type, component);
			
			return component;
        }
        public removeComponent<K extends Entity>(type:{new():K}):K
		{
			if (this.isDisposed)
			{
				return;
			}

			if (this._components == null)
			{
				return;
			}
			
			let c = this.getComponentWithType(type);
			if (c == null)
			{
				return;
			}

			this.removeFromComponent(type, c);
			c.dispose();
        }
        public removeComponentWithInstance(component:Entity):void
		{
			if (this.isDisposed)
			{
				return;
			}
			
			if (this._components == null)
			{
				return;
			}

			let type = component.getType();
			let c = this.getComponentWithType(type);
			if (c == null)
			{
				return;
			}
			if (c.instanceId != component.instanceId)
			{
				return;
			}
			
			this.removeFromComponent(type, c);
			c.dispose();
        }
        public removeComponentWithType(type:{new()}):void
		{
			if (this.isDisposed)
			{
				return;
			}
			
			let c = this.getComponentWithType(type);
			if (c == null)
			{
				return;
			}

			this.removeFromComponent(type, c);
			c.dispose();
        }
        public  getComponent<K extends Entity>(type:{new():K}):K
		{
			if (this._components == null)
			{
				return null;
			}
			let component=this._components.get(type);
            return component as K;
		}
        public getComponentWithType(type:{new()}):Entity
		{
			if (this._components == null)
			{
				return null;
			}
            let component=this._components.get(type);
            
			return component;
		}
        equals(other: Entity): boolean {
            return this.instanceId == other.instanceId;
        }
        getHashCode(): number {
            return this.instanceId;
        }

		private createWithComponentParent1<T extends Entity>(type:{new():T},isFromPool:boolean = true):T 
		{
			let component:T;
			if (!isFromPool)
			{
				component = new type();
			}
			else
			{
				component = game.objectPool.fetch(type);	
			}
			component.domain = this.domain;
			component.id =this.id;
			component.componentParent = this;
			
			game.event.awake(component);
			return component;
		}

		private createWithComponentParent2<T extends Entity,A>(type:{new():T},a:A,isFromPool:boolean = true):T 
		{
			let component:T;
			if (!isFromPool)
			{
				component = new type();
			}
			else
			{
				component = game.objectPool.fetch(type);	
			}
			component.domain = this.domain;
			component.id =this.id;
			component.componentParent = this;
			
			game.event.awake(component,a);
			return component;
		}
		private createWithComponentParent3<T extends Entity,A,B>(type:{new():T},a:A,b:B,isFromPool:boolean = true):T 
		{
			let component:T;
			if (!isFromPool)
			{
				component = new type();
			}
			else
			{
				component = game.objectPool.fetch(type);	
			}
			component.domain = this.domain;
			component.id =this.id;
			component.componentParent = this;
			
			game.event.awake(component,a,b);
			return component;
		}

		private createWithComponentParent4<T extends Entity,A,B,C>(type:{new():T},a:A,b:B,c:C,isFromPool:boolean = true):T 
		{
			let component:T;
			if (!isFromPool)
			{
				component = new type();
			}
			else
			{
				component = game.objectPool.fetch(type);	
			}
			component.domain = this.domain;
			component.id =this.id;
			component.componentParent = this;
			
			game.event.awake(component,a,b,c);
			return component;
        }
        public as<T extends Entity>():T{
            return ((this as any) as T);
        }
        protected awake(a?,b?,c?):void{};
        protected start():void{

        }
        protected update(dt:number){
            
        }
        protected lateUpdate(){

        }
        protected destroy(){

        }
        protected onEnable(){

        }
        protected onDisable(){
            
        }
    }
}